/**
 * @file Implements EventSet.
 * @author Zmei Zelen <zmei.zelen@gmail.com>
 */


/**
 * @constructor
 * @returns {undefined}
 */
function EventSet() {
    this.events = new ObjectContainer();
}


/**
 * List of reserved words. Can't be used for event names.
 * @type Array
 */
EventSet.prototype.reserved = ['add', 'get', 'remove', 'events', 'reserved', 'fire'];


/**
 * Links event hadler to a custom event. 
 * 
 * @throws {Error} 
 * @param {string|object} event Event name, list of names or associative array: { name: handler, ... }.
 * @param {function} handler Optional handler for the specified event or list of events.
 * @returns {undefined} 
 */
EventSet.prototype.add = function (event, handler) {
    
    function _add(event, handler) {
        var object;

        if (this.reserved.indexOf(event) !== -1) {
            var error = new Error("The word '" + event + "' is reserved.");
            error.name = 'EventSet';
            throw error;       
        }

        if (!(event in this)) {
            object = new ObjectContainer();
            this.events.add(object, event);
            this[event] = function (handler) {             
                if (typeof handler === 'function') {
                    var container = this.events.get(event);
                    if (container) container.add(handler);
                }
            };        
        }

        this[event](handler);        
    }
    
    if (typeof event === 'object') {        
        if (event instanceof Array) {                       
            for (var id = 0, len = event.length; id < len; id++) {                
                _add.call(this, event[id], handler);
            }            
        } else {           
            for (var id in event) _add.call(this, id, event[id]);           
        }
    } else return _add.call(this, event, handler);    
};


/**
 * Binds and returns event source as bound function.
 * 
 * @param {string} event Event name.
 * @returns {function} Event source function.
 */
EventSet.prototype.get = function (event) {
    if (event in this) {
        return this[event].bind(this);
    } else return undefined;
};


/**
 * Removes an event handler or a whole event from the set.
 * 
 * @param {string|function} handler Event handler or event name.
 * @returns {undefined}
 */
EventSet.prototype.remove = function (handler) {
    if (typeof handler === 'string' && this.events.indexOf(handler) !== -1) {
        this.events.remove(handler);
        delete this[handler];
    } else {
        this.events.each(function (event) {
            event.remove(handler);
        });
    }
};


/**
 * Fires an event.
 * 
 * @param {string} Event name.
 * @param {...*} Arguments to be passed to the handlers.
 * @returns {array|undefined} Array of handler results or nothing if the event doesn't exist.
 */
EventSet.prototype.fire = function (/* ... */) {
    var container = this.events.get(arguments[0]);
    if (container) {
        var _arguments = Array.prototype.slice.call(arguments);
        _arguments.shift();       
        return container.apply(_arguments);
    }
};
